/*
 * MP3_Beaney - The MP3 Tag Editor is a free ID3-Tag Editor
 * Copyright (C) 2012 Bernhard Teismann, André Seipelt, Steffen Gruschka, Imanuel Rohlfing
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package model;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.audio.mp3.MP3File;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.id3.ID3v23Tag;
import org.jaudiotagger.tag.images.Artwork;
import org.jaudiotagger.tag.images.ArtworkFactory;

/**
 * Manages all MP3 Files, their file names and the directory they are in.
 * <p/>
 * @author Steffen Gruschka
 * @author Bernhard Teismann
 * @author André Seipelt
 * @author Imanuel Rohlfing
 * @version 1.0
 */
public class MP3Management {

    private ArrayList container;                // Container für die MP3-Objekte
    private ArrayList exceptions;               // Container für die MP3Exception-Objekte
    private MP3Player player;                   // MP3Player-Objekt um die MP3s wiederzugeben
    final static String NO_FILES_SELECTED = "Dieser Ordner enthält keine MP3-Dateien";
    final static String STRING_TO_INT = "Die Track-Nummer wurde nicht erkannt";
    final static String CANNOT_READ = "Die Datei kann nicht gelesen werden";
    final static String CANNOT_MOVE = "Die Datei kann nicht verschoben werden";
    final static String CANNOT_WRITE = "Die Datei kann nicht geaendert werden";
    final static String CANNOT_DELETE = "Die Datei kann nicht geloescht werden";
    final static String CANNOT_CHANGE_DIRNAME = "Der Ordnername kann nicht geaendert werden";
    final static String CANNOT_CHANGE_FILENAME = "Der Dateiname kann nicht geaendert werden";
    final static String READ_ONLY = "Die Datei kann nicht geaendert werden";
    final static String INVALID_AUDIOFRAME = "Der Audio-Frame der Datei ist fehlerhaft";
    final static String INVALID_TAG = "Der Tag-Frame der Datei ist fehlerhaft";
    final static String IO = "Die Datei kann nicht gelesen/geaendert werden";
    final static String PLAYER_CANNOT_PLAY = "Die Datei kann nicht abgespielt werden";
    final static String PLAYER_CANNOT_OPEN = "Die Datei kann nicht abgespielt werden";
    final static String PLAYER_CANNOT_STOP = "Die Datei kann nicht angehalten werden";
    final static String COVER_ART = "Das Cover kann nicht ausgelesen werden.";

    /**
     * Constructor. Creates containers for files, exceptions and the MP3 Player
     * Before the files are added to the container, it is checked whether they are in fact MP3 Files
     * <p/>
     * @param input path to the MP3 File or its folder as string
     * @throws MP3Exception
     */
    public MP3Management(String input) throws MP3Exception{
        //Initialize containers
        container = new ArrayList();
        exceptions = new ArrayList();
        player = new MP3Player();

        //Save path temporarily
        File file = new File(input);

        // If an MP3 File has been passed: 
        //-> Create MP3 Object and add it to the container
        if (file.isFile()){
            if (file.getName().endsWith(".mp3")){
                addToContainer(file);
            }
        } //If a folder has been passed
        else if (file.isDirectory()){

            //List all files and folders contained in it
            File[] files = file.listFiles();

            //and check if MP3 Files are in them
            boolean filesExist = false;
            for (File f : files){
                if (f.isFile() && f.getName().endsWith(".mp3")){
                    filesExist = true;
                }
            }
            // Falls dies der Fall ist:
            // -> Erzeuge MP3-Objekte und füge diese dem Container hinzu
            if (files != null && filesExist){
                for (int i = 0; i < files.length; i++){
                    if (files[i].isFile() && files[i].getName().endsWith(".mp3")){
                        addToContainer(files[i]);
                    }
                }
            } // Ansonsten gebe Fehler Zurück, dass keine Dateien ausgewählt wurden
            else{
                MP3Exception exception = new MP3Exception(NO_FILES_SELECTED, null);
                exceptions.add(exception);
                throw exception;
            }
        }else{
            MP3Exception exception = new MP3Exception(NO_FILES_SELECTED, null);
            exceptions.add(exception);
            throw exception;
        }
    }

    //  -----------------------------------------
    //  Hilfsklasse, welche ein MP3-Objekt erzeugt
    //  und dieses dem Container hinzufügt
    //  input:  Pfad zur MP3-Datei / Ordner (File)
    //  -----------------------------------------    
    private void addToContainer(File file){
        MP3 mp3 = new MP3();

        try{
            // Wandle die Datei in ein MP3File-Objekt um (Klasse der jAudioTagger-Library)
            MP3File mp3File = (MP3File) AudioFileIO.read(file);

            // Falls ID3-Tags vorhanden sind füge die Eigenschaften
            // dem MP3-Objekt hinzu
            if (mp3File.hasID3v2Tag() || mp3File.hasID3v1Tag()){
                Tag tags = mp3File.getTag();
                mp3.setArtist(tags.getFirst(FieldKey.ARTIST));
                mp3.setTitle(tags.getFirst(FieldKey.TITLE));
                mp3.setAlbum(tags.getFirst(FieldKey.ALBUM));
                mp3.setRelease(tags.getFirst(FieldKey.YEAR));
                mp3.setGenre(tags.getFirst(FieldKey.GENRE));
                mp3.setComment(tags.getFirst(FieldKey.COMMENT));
                mp3.setTrack(tags.getFirst(FieldKey.TRACK));
                mp3.setPath(file.getAbsolutePath());

                // Versuche gesondert das Cover auszulesen
                try{
                    Artwork image = tags.getFirstArtwork();
                    if (image != null){
                        if (image.isLinked()){
                            mp3.setCover(new File(image.getImageUrl()));
                        }else{
                            mp3.setCover(image);
                        }
                    }
                }catch (MP3Exception ex){
                    exceptions.add(ex);
                }
            } // Falls keine ID3-Tags vorhanden sind initialisiere das MP3-Objekt
            // mit dem Pfadnamen als Titel
            else{
                mp3.setTitle(file.getName());
                mp3.setPath(file.getAbsolutePath());
            }

            // Füge anschließend das Objekt  dem Container hinzu
            container.add(mp3);

        }catch (CannotReadException ex){
            MP3Exception exception = new MP3Exception(CANNOT_READ, file);
            exceptions.add(exception);
        }catch (IOException ex){
            MP3Exception exception = new MP3Exception(IO, file);
            exceptions.add(exception);
        }catch (TagException ex){
            MP3Exception exception = new MP3Exception(INVALID_TAG, file);
            exceptions.add(exception);
        }catch (ReadOnlyFileException ex){
            MP3Exception exception = new MP3Exception(READ_ONLY, file);
            exceptions.add(exception);
        }catch (InvalidAudioFrameException ex){
            MP3Exception exception = new MP3Exception(INVALID_AUDIOFRAME, file);
            exceptions.add(exception);
        }
    }

    /**
     * Getter for path.
     * <p/>
     * @return formatted string from path
     */
    public File getPath(){
        return ((MP3) container.get(0)).getPath().getParentFile();
    }

    /**
     * Checks if the container is empty.
     * <p/>
     * @return true if container is empty, false if not
     */
    public boolean isEmpty(){
        return (container.isEmpty());
    }

    /**
     * Returns the container's size.
     * <p/>
     * @return container's size as integer
     */
    public int size(){
        return container.size();
    }

    /**
     * Gets an MP3 Object from the container in order to be able to edit it.
     * <p/>
     * @param i MP3's position in the container
     * @return MP3 object if found, null if not
     * @throws MP3Exception
     */
    public MP3 get(int i) throws MP3Exception{
        if (i >= 0 || i < container.size()){
            return (MP3) container.get(i);
        }else{
            return null;
        }
    }

    /**
     * Writes an MP3Exception into a text file.
     * <p/>
     * @param file path to the generated text file
     */
    public void writeExceptions(String file){
        // neues BufferedWriter-Objekt um die Datei zu beschreiben
        BufferedWriter out = null;

        //If a path has been specified
        if (!file.equals("")){
            try{
                out = new BufferedWriter(new FileWriter(file));

                // Aktuelles Datum
                Date datum = new Date();
                SimpleDateFormat myDateFormat = new SimpleDateFormat("dd.MM.yyyy, HH:mm");

                // Schreibe Header in die .txt-Datei
                out.write("---------------------------------------------------");
                out.newLine();
                out.write("----- MP3-Beaney - Der Tag Editor");
                out.newLine();
                out.write("----- Fehlerprotokoll vom " + myDateFormat.format(datum));
                out.newLine();
                out.write("---------------------------------------------------");
                out.newLine();
                out.newLine();

                // Falls keine Fehler aufgetreten sind schreibe dies in die Datei
                if (exceptions.isEmpty()){
                    out.write("  Es sind keine Fehler aufgetreten");
                } // ansonsten schreibe jeden Fehler in die Datei
                else{
                    for (int i = 0; i < exceptions.size(); i++){
                        out.write("  " + ((MP3Exception) exceptions.get(i)).get());
                        out.newLine();
                    }
                }
            }catch (Exception ex){
            }finally{
                try{
                    // und schließe diese danach
                    if (out != null){
                        out.flush();
                        out.close();
                    }
                }catch (Exception ex){
                }
            }
        }
    }

    /**
     * Plays an MP3 File.
     * <p/>
     * @param i MP3's position in the container
     * @throws MP3Exception
     */
    public void playMP3(int i) throws MP3Exception{
        try{
            player.play(((MP3) container.get(i)).getPath().getAbsolutePath());
        }catch (MP3Exception ex){
            exceptions.add(ex);
            throw ex;
        }
    }

    /**
     * Stops the MP3 playback.
     * <p/>
     * @param i MP3's position in the container
     * @throws MP3Exception
     */
    public void stopMP3(int i) throws MP3Exception{
        try{
            player.stop();
        }catch (MP3Exception ex){
            exceptions.add(ex);
            throw ex;
        }
    }

    /**
     * Checks the MP3Player is playing
     * <p/>
     * @see MP3Player#isPlaying()
     * @return true if MP3Player is playing
     */
    public Boolean isMP3Playing(){
        return player.isPlaying();
    }

    /**
     * Saves the MP3 Objects' current configuration.
     * <p/>
     * @throws MP3Exception
     */
    public void save() throws MP3Exception{
        MP3 mp3 = null;

        for (int i = 0; i < container.size(); i++){
            mp3 = (MP3) container.get(i);
            try{
                MP3File mp3File = (MP3File) AudioFileIO.read(mp3.getPath());
                mp3File.setTag(new ID3v23Tag());
                Tag v24 = mp3File.getTag();

                if (mp3.getCover() != null){
                    try{
                        Artwork cov = ArtworkFactory.createArtworkFromFile(new File(mp3.getCover().getAbsolutePath()));
                        cov.setPictureType(5);
                        v24.setField(cov);
                    }catch (IOException ex){
                        MP3Exception exception = new MP3Exception(COVER_ART, mp3.getPath());
                        exceptions.add(exception);
                    }

                    if (!mp3.getCover().delete()){
                        MP3Exception exception = new MP3Exception(CANNOT_DELETE, mp3.getPath());
                        exceptions.add(exception);
                    }
                }

                v24.setField(FieldKey.ARTIST, mp3.getArtist());
                v24.setField(FieldKey.TITLE, mp3.getTitle());
                v24.setField(FieldKey.ALBUM, mp3.getAlbum());
                v24.setField(FieldKey.GENRE, mp3.getGenre());
                v24.setField(FieldKey.YEAR, mp3.getRelease());

                try{
                    v24.setField(FieldKey.TRACK, mp3.getTrack());
                }catch (Throwable t){
                    v24.setField(FieldKey.TRACK, "-1");
                }
                v24.setField(FieldKey.COMMENT, mp3.getComment());

                mp3File.commit();

            }catch (CannotReadException ex){
                MP3Exception exception = new MP3Exception(CANNOT_READ, mp3.getPath());
                exceptions.add(exception);
            }catch (TagException ex){
                MP3Exception exception = new MP3Exception(INVALID_TAG, mp3.getPath());
                exceptions.add(exception);
            }catch (ReadOnlyFileException ex){
                MP3Exception exception = new MP3Exception(READ_ONLY, mp3.getPath());
                throw exception;
            }catch (InvalidAudioFrameException ex){
                MP3Exception exception = new MP3Exception(INVALID_AUDIOFRAME, mp3.getPath());
                exceptions.add(exception);
            }catch (CannotWriteException ex){
                MP3Exception exception = new MP3Exception(CANNOT_WRITE, mp3.getPath());
                exceptions.add(exception);
            }catch (IOException ex){
                MP3Exception exception = new MP3Exception(IO, mp3.getPath());
                exceptions.add(exception);
            }
        }
    }

    /**
     * Changes the MP3 Files' folder name.
     * <p/>
     * @param changes new folder name
     * @throws MP3Exception
     */
    public void setPath(String changes) throws MP3Exception{
        MP3 mp3 = (MP3) container.get(0);                    // Erstes MP3-Objekt im Container
        File dir = mp3.getPath().getParentFile();      // Pfad zum alten ordner

        String temp = changes;                                       // Ersetzen der Platzhalter
        temp = temp.replaceAll("(<Künstler>)", mp3.getArtist());
        temp = temp.replaceAll("(<Nummer>)", mp3.getTrack());
        temp = temp.replaceAll("(<Titel>)", mp3.getTitle());
        temp = temp.replaceAll("(<Album>)", mp3.getAlbum());
        temp = temp.replaceAll("(<Jahr>)", mp3.getRelease());
        temp = temp.replaceAll("[^\\w\\s-_ÄäÖöÜü\\.]*", "");

        if (dir.renameTo(new File(dir.getParentFile().getAbsolutePath() + File.separator + temp))){
            for (int i = 0; i < container.size(); i++){
                mp3 = (MP3) container.get(i);
                mp3.setPath(dir.getParent() + File.separator + temp + File.separator + mp3.getFileName());
                if (mp3.getCoverAsImage() != null){
                    mp3.setCover(new File(mp3.getCover().getParentFile().getParent()
                            + File.separator
                            + temp
                            + File.separator
                            + mp3.getCover().getName()));
                }
            }
        }else{
            MP3Exception exception = new MP3Exception(CANNOT_CHANGE_DIRNAME, new File(dir.getAbsolutePath()));
            exceptions.add(exception);
            throw exception;
        }
    }

    /**
     * Changes the MP3Files' file names.
     * <p/>
     * @param changes new file names
     * @throws MP3Exception
     */
    public void setFileNames(String changes) throws MP3Exception{
        MP3 mp3 = null;
        String temp = "";
        String newPath = "";
        for (int i = 0; i < container.size(); i++){
            mp3 = (MP3) container.get(i);

            temp = changes;
            temp = temp.replaceAll("(<Künstler>)", mp3.getArtist());
            temp = temp.replaceAll("(<Nummer>)", mp3.getTrack());
            temp = temp.replaceAll("(<Titel>)", mp3.getTitle());
            temp = temp.replaceAll("(<Album>)", mp3.getAlbum());
            temp = temp.replaceAll("(<Jahr>)", mp3.getRelease());
            temp = temp.replaceAll("[^\\w\\s-_ÄäÖöÜü\\.]*", "");

            newPath = mp3.getPath().getParent() + File.separator + temp + ".mp3";
            if (!mp3.getPath().renameTo(new File(newPath))){
                if (!mp3.getPath().renameTo(new File(mp3.getPath().getParent() + File.separator + temp + i + ".mp3"))){
                    MP3Exception exception = new MP3Exception(CANNOT_CHANGE_FILENAME, mp3.getPath());
                    exceptions.add(exception);
                    throw exception;
                }
            }
        }

    }
}
